import { Config, Inject, Provide } from '@midwayjs/decorator';
import { BaseService, CoolCommException } from '@cool-midway/core';
import { CacheManager } from '@midwayjs/cache';
import * as crypto from 'crypto';
import axios from 'axios';

/**
 * 微信PAI
 */
@Provide()
export class WXService extends BaseService {
    @Config('module.app.wx')
    wxConfig;

    @Inject()
    cacheManager: CacheManager;

    /**
     * 获取 session_key
     * @param code 登录时获取的 code
     * @returns Promise 对象
     */
    async getSessionKey(code: string) {
        try {
            const { appid, secret } = this.wxConfig.mini;
            const { data } = await axios.get(`https://api.weixin.qq.com/sns/jscode2session?appid=${appid}&secret=${secret}&js_code=${code}&grant_type=authorization_code`);
            if (data.errcode) {
                throw new CoolCommException(data.errmsg);
            }
            return data;   
        } catch (error) {
            throw new CoolCommException('获取session失败，请重试');
        }
    }

    /**
     * 接口调用凭证
     */
    async getAccessToken() {
        try {
            const access_token: any = await this.cacheManager.get('access:token');
            if (access_token) {
                return access_token;
            }
            const { appid, secret } = this.wxConfig.mini;
            const { data } = await axios.get(`https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appid}&secret=${secret}`);
            if (data.errcode) {
                console.log(data, '获取接口调用凭证失败');
                return false;
            }
            await this.cacheManager.set('access:token', data.access_token, { ttl: data.expires_in });
            return data.access_token
        } catch (error) {
            throw new CoolCommException('获取Token失败，请重试');
        }
    }

    /**
     * 获取手机号
     * @param code string
     */
    async getPhoneNumber(code: string) {
        try {
            const access_token = await this.getAccessToken();
            if (!access_token) return false;
            const { data } = await axios.post(`https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=${access_token}`, {
                code,
            }, {
                headers: {
                    'Content-Type': 'application/json'
                }
            });
            if (data.errcode) {
                console.log('获取手机号失败');
                return false;
            }
            return data.phone_info.phoneNumber;
        } catch (error) {
            throw new CoolCommException('获取手机号失败');
        }
    }

    /**
     * 模板消息
     * @param touser string 接收者（用户）的 openid
     * @param template_id string 所需下发的订阅模板id
     * @param page string 点击模板卡片后的跳转页面，仅限本小程序内的页面。支持带参数,（示例index?foo=bar）。该字段不填则模板无跳转。
     * @param info object 模板内容，格式形如 { "key1": { "value": any }, "key2": { "value": any } }
     */
    async sendTemplateMessage(touser: string, template_id: string, page: string, info: object) {
        try {
            const access_token = await this.getAccessToken();
            if (!access_token) return false;
            const { data } = await axios.post(`https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=${access_token}`, {
                touser,
                template_id,
                url: 'https://cuui.cn',
                miniprogram: {
                    appid: this.wxConfig.mini.appid,
                    pagepath: page
                },
                data: info
            }, {
                headers: {
                    'Content-Type': 'application/json'
                }
            });
            if (data.errcode) {
                return false;
            }
            return true;
        } catch (error) {
            throw new CoolCommException('发送模板消息失败');
        }
    }

    /**
     * 用户信息
     * @param code 登录时获取的 code
     * @param encryptedData 包括敏感数据在内的完整用户信息的加密数据
     * @param iv 加密算法的初始向量
     * @returns Promise 对象
     */
    async getUserInfo(code: string, encryptedData: string, iv: string) {
        try {
            const { session_key } = await this.getSessionKey(code);
            const userInfo = await this.decryptData(encryptedData, iv, session_key);
            if (userInfo) {
                delete userInfo['watermark'];
                return {
                    nickname: userInfo['nickName'] as string,
                    avatar: userInfo['avatarUrl'] as string,
                    openid: userInfo['openId'] as string
                }
            }
            return null;
        } catch (error) {
            throw new CoolCommException('解密用户信息失败，请重试');
        }
    }

    /**
     * 信息解密
     * @param encryptedData  包括敏感数据在内的完整用户信息的加密数据
     * @param iv 加密算法的初始向量
     * @param sessionKey 会话密钥
     * @returns Promise 对象
     */
    async decryptData(encryptedData: any, iv: any, sessionKey: any) {
        sessionKey = Buffer.from(sessionKey, 'base64');
        encryptedData = Buffer.from(encryptedData, 'base64');
        iv = Buffer.from(iv, 'base64');
        try {
            const decipher = crypto.createDecipheriv('aes-128-cbc', sessionKey, iv);
            decipher.setAutoPadding(true);
            let decoded = decipher.update(encryptedData, 'binary', 'utf8');
            decoded += decipher.final('utf8');
            decoded = JSON.parse(decoded);
            return decoded;
        } catch (error) {
            throw new CoolCommException('登录失败，请重试');
        }
    }
}
